package com.airbnb.chancery;
import com.airbnb.chancery.github.GithubClient;
import com.airbnb.chancery.model.CallbackPayload;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Timer;
import com.yammer.metrics.core.TimerContext;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
@Slf4j
public class S3Archiver extends FilteringSubscriber {
@NonNull
private final AmazonS3Client s3Client;
@NonNull
private final String bucketName;
@NonNull
private final GithubClient ghClient;
@NonNull
private final PayloadExpressionEvaluator keyTemplate;
@NonNull
private final Timer uploadTimer = Metrics.newTimer(getClass(), "upload",
TimeUnit.SECONDS, TimeUnit.SECONDS);
@NonNull
private final Timer deleteTimer = Metrics.newTimer(getClass(), "delete",
TimeUnit.SECONDS, TimeUnit.SECONDS);
public S3Archiver(@NotNull S3ArchiverConfig config,
@NotNull AmazonS3Client s3Client,
@NotNull GithubClient ghClient) {
super(config.getRefFilter());
this.s3Client = s3Client;
this.ghClient = ghClient;
bucketName = config.getBucketName();
keyTemplate = new PayloadExpressionEvaluator(config.getKeyTemplate());
}
@Override
Logger getLogger() {
return log;
}
@Override
protected void handleCallback(@NotNull CallbackPayload callbackPayload) throws Exception {
final String key = keyTemplate.evaluateForPayload(callbackPayload);
if (callbackPayload.isDeleted())
delete(key);
else {
final Path path;
final String hash = callbackPayload.getAfter();
final String owner = callbackPayload.getRepository().getOwner().getName();
final String repoName = callbackPayload.getRepository().getName();
path = ghClient.download(owner, repoName, hash);
upload(path.toFile(), key, callbackPayload);
try {
Files.delete(path);
} catch (IOException e) {
log.warn("Couldn't delete {}", path, e);
}
}
}
private void delete(@NotNull String key) {
log.info("Removing key {} from {}", key, bucketName);
final TimerContext time = deleteTimer.time();
try {
s3Client.deleteObject(bucketName, key);
} catch (Exception e) {
log.error("Couldn't delete {} from {}", key, bucketName, e);
throw e;
} finally {
time.stop();
}
log.info("Deleted {} from {}", key, bucketName);
}
private void upload(@NotNull File src, @NotNull String key, @NotNull CallbackPayload payload) {
log.info("Uploading {} to {} in {}", src, key, bucketName);
final PutObjectRequest request = new PutObjectRequest(bucketName, key, src);
final ObjectMetadata metadata = request.getMetadata();
final String commitId = payload.getAfter();
if (commitId != null) {
metadata.addUserMetadata("commit-id", commitId);
}
final DateTime timestamp = payload.getTimestamp();
if (timestamp != null) {
metadata.addUserMetadata("hook-timestamp",
ISODateTimeFormat.basicTime().print(timestamp));
}
final TimerContext time = uploadTimer.time();
try {
s3Client.putObject(request);
} catch (Exception e) {
log.error("Couldn't upload to {} in {}", key, bucketName, e);
throw e;
} finally {
time.stop();
}
log.info("Uploaded to {} in {}", key, bucketName);
}
}